
#include "Shader.h"


Shader::Shader(string filename, void(*bindFunc)(GLuint))
{
	this->createShaderFromFile(filename, bindFunc);
}

void Shader::bind()
{
	glUseProgram(_shaderProgram);
}

void Shader::unBind()
{
	glUseProgram(0);
}

void Shader::applyUniform()
{

}

void Shader::createShaderFromFile(string filename, void(*bindFunc)(GLuint))
{
	GLuint vertShader, fragShader, resultShader;
    string vsFilepath, fsFilepath;
    
    // Create shader program.
    resultShader = glCreateProgram();
    
    // Create and compile vertex shader.
    vsFilepath = filename + ".vsh";
    if (!compileShader(&vertShader, GL_VERTEX_SHADER, vsFilepath)) {
        cout<<"ERROR: Failed to compile vertex shader "<<vsFilepath<<endl;
		_shaderProgram = UNDEF;
        return;
    }
    
    // Create and compile fragment shader.
    fsFilepath = filename + ".fsh";
    if (!compileShader(&fragShader, GL_FRAGMENT_SHADER, fsFilepath)) {
        cout<<"ERROR: Failed to compile fragment shader "<<fsFilepath<<endl;
        _shaderProgram = UNDEF;
        return;
    }
    
    // Attach vertex shader to program.
    glAttachShader(resultShader, vertShader);
    
    // Attach fragment shader to program.
    glAttachShader(resultShader, fragShader);
    
    // Bind attribute locations.
    // This needs to be done prior to linking.
	bindFunc(resultShader);
	GL_CHECK_ERROR;

    // Link program.
    if (!linkProgram(resultShader)) {
        cout<<"ERROR: Failed to link program"<<endl;
        
        if (vertShader) {
            glDeleteShader(vertShader);
            vertShader = 0;
        }
        if (fragShader) {
            glDeleteShader(fragShader);
            fragShader = 0;
        }
        if (resultShader) {
            glDeleteProgram(resultShader);
            resultShader = 0;
        }
        
		_shaderProgram = UNDEF;
        return;
    }
    
    // Release vertex and fragment shaders.
    if (vertShader) {
        glDetachShader(resultShader, vertShader);
        glDeleteShader(vertShader);
    }
	
    if (fragShader) {
        glDetachShader(resultShader, fragShader);
        glDeleteShader(fragShader);
    }
	
    _shaderProgram = resultShader;
}

bool Shader::compileShader(GLuint* shader, GLenum type, string file)
{
    GLint status;
    const GLchar *source;
    
	source = getShaderSource(file);
    if (!source) {
        cout<<"ERROR: Failed to load vertex shader"<<endl;
        return false;
    }
    
    *shader = glCreateShader(type);
    glShaderSource(*shader, 1, &source, NULL);
    glCompileShader(*shader);
    delete[] source;

    GLint logLength;
    glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &logLength);
    if (logLength > 1) {
        GLchar *log = new GLchar[logLength];
        glGetShaderInfoLog(*shader, logLength, &logLength, log);
        cout<<"Shader compile log:\n"<<log<<endl;
        delete[] log;
    }

    
    glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);
    if (status == 0) {
        glDeleteShader(*shader);
        return false;
    }
    
    return true;
}

bool Shader::linkProgram(GLuint prog)
{
    GLint status;
    glLinkProgram(prog);
    

    GLint logLength;
    glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength);
    if (logLength > 1) {
        GLchar *log = new GLchar[logLength];
        glGetProgramInfoLog(prog, logLength, &logLength, log);
        cout<<"Program link log:\n"<<log<<endl;
        delete[] log;
    }

    
    glGetProgramiv(prog, GL_LINK_STATUS, &status);
    if (status == 0)
        return false;
    
    return true;
}

bool Shader::validateProgram(GLuint prog)
{
    GLint logLength, status;
    
    glValidateProgram(prog);
    glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength);
    if (logLength > 1) {
        GLchar *log = new GLchar[logLength];
        glGetProgramInfoLog(prog, logLength, &logLength, log);
        cout<<"Program validate log:\n"<<log<<endl;
        delete[] log;
    }
    
    glGetProgramiv(prog, GL_VALIDATE_STATUS, &status);
    if (status == 0) 
        return false;
    
    return true;
}

GLchar* Shader::getShaderSource(string source)
{
	ifstream fileStream;
	int fileSize;
	GLchar* buffer;

	fileStream.open(source, ios_base::in | ios_base::binary);
	if (!fileStream.is_open()) {
		cout<<"ERROR cRHResourceHandler: file is not open"<<endl;
		return nullptr;
	}

	fileStream.seekg(0, ios_base::end);
	fileSize = fileStream.tellg();
	fileStream.seekg(0, ios_base::beg);

	buffer = new char[fileSize + 1];
	fileStream.read(buffer, fileSize);
	fileStream.close();

	buffer[fileSize] = '\0';

	return buffer;
}
